package attribute;

import item.*;
import item.pickable.*;
import iterator.*;

import exceptions.*;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author John Breyssem	1Ba Informatica		r0303394
 * 
 * @author Lars Feyaerts	1Ba Informatica		r0296831
 * 
 * @version	1.3
 *
 */
import be.kuleuven.cs.som.annotate.*;
/**
 * 	@invar	The width and height of this board must at all time be a valid width and height.
 * 				|isValidWidthAndHeight(this.getWidth(), this.getHeight())
 * 
 *  @invar ...
 *  			|hasProperPositions()
 */
public class Board{
	private final long width, height;
	private static final long MIN_OF_BOARD = 0;
	private static final long MAX_OF_BOARD = Long.MAX_VALUE;
	private ArrayList<Position> listOfPositions = new ArrayList<Position>();
	private ArrayList<Item> listOfItemsOnBoard = new ArrayList<Item>();
	private boolean isTerminated;
	
	/**
	 * Initialises a new board with the given width and the given height.
	 * 
	 * @param width
	 * 			The new width the board must have.
	 * @param height
	 * 			The new height the board must have.
	 * @effect ...
	 * 			|setValidWidthAndHeight(width, height)
	 */
	public Board(long width, long height){
		this.width = Math.abs(width%(getMaxOfBoard() - getMinOfBoard()+ 1));
		this.height = Math.abs(height%(getMaxOfBoard() - getMinOfBoard() + 1));
	}
	
	/**
	 *Returns the MIN_OF_BOARD.
	 *	|return this.MIN_OF_BOARD
	 */
	@Basic @Immutable 
	public static long getMinOfBoard(){
		return MIN_OF_BOARD;
	}
	
	/**
	 * Returns the MAX_OF_BOARD
	 * 	|return this.MAX_OF_BOARD
	 */
	@Basic @Immutable
	public static long getMaxOfBoard(){
		return MAX_OF_BOARD;
	}
	
	/**
	 * Checks whether the given height and width are valid
	 * 
	 * @param width		The width that has to be checked
	 * 
	 * @param height	The height that has to be checked.
	 * 
	 * @return	...
	 * 			|result == width <= getMaxOfBoard() - getMinOfBoard()+ 1 
	 *			|&& height <= getMaxOfBoard() - getMinOfBoard() + 1
	 */
	public static boolean isValidWidthAndHeight(long width, long height){
		return width <= getMaxOfBoard() - getMinOfBoard()+ 1 
				&& height <= getMaxOfBoard() - getMinOfBoard() + 1; //want ook min telt mee.
	}
	
	/**
	 * ...
	 * 
	 * @param board2
	 * 			...
	 * 
	 * @post	...
	 * 			|for(Position position: board2.listOfPosition)
	 * 			|	for(Item item: position.listOfItemsOnPosition)
	 * 			|		if(item.new.getBoard() != null){
	 * 			|			item.new.getPosition().getBoard() == this
	 * 			|			item.new.getPosition() == position
	 * 			|		}
	 * 			|		else	
	 * 			|			item.new.isTerminated == true
	 * 
	 * @post	...
	 * 			|board2.new.isTerminated() == true
	 * 
	 * @throws IllegalArgumentException
	 * 			...	
	 * 			|board2 == null
	 * @throws TerminatedObjectException
	 * 			...
	 * 			|board2.isTerminated() || this.isTerminated()
	 */
	public void merge(Board board2) throws IllegalArgumentException, TerminatedObjectException{
		try{
			if(board2 == null)
				throw new IllegalArgumentException();
			if(board2.isTerminated())
				throw new TerminatedObjectException(board2);
			if(this.isTerminated())
				throw new TerminatedObjectException(this);
			for(Position position: board2.listOfPositions){
				for(Item item: position.listOfItemsOnPosition){
					item.setValidPosition(position.getX(), position.getY(), this);
									if(!item.getIsOnABoard()){
										item.terminate();
									}
				}
			}
			board2.terminate();
		}
		catch(IllegalArgumentException exc){
			System.out.println("The given board to merge with does not exist");
		}
		catch(TerminatedObjectException exc){
			System.out.println("One of the boards to merge is terminated");
		}
	}

	/**
	 * Searches for all robots in list of items on board.
	 * @return ...
	 * 			|for each robot ion listOfItemsOnBoard:
	 * 			|	robot.add((Robot)item)
	 * 			|result == robots
	 */
	public ArrayList<Robot> getListOfRobots(){
		ArrayList<Robot> robots = new ArrayList<Robot>();
        for (Item item : this.getListOfItemsOnBoard()) {
                if (item instanceof Robot) {
                        robots.add((Robot) item);
                }
        }
        return robots;
	}
	
	/**
	 * Returns the width of this board.
	 * 
	 * @return	The width of this board.
	 */
	@Basic @Immutable
	public long getWidth() {
		if(!this.isTerminated())
			return width;
		else
			return -1;
	}

	/**
	 * Returns the height of this board.
	 * 
	 * @return 	The height of this board.
	 */
	@Basic @Immutable
	public long getHeight() {
		if(!this.isTerminated())
			return height;
		else
			return -1;
	}
	
	/**
	 * Terminates the given board and the items that are on it.
	 * 
	 * @effect 	...
	 * 			|for each position in getListOfPostitions() 
	 * 			|	for each item in position.getListOfItemsOnPosition()
	 * 			|		item.terminate()
	 * 
	 * @post	...
	 * 			|this.new.isTerminated() == true
	 */
	public void terminate(){
		if(!this.isTerminated()){
			for(Position position: this.listOfPositions){
				for(Item item : position.getListOfItemsAtPosition())
					listOfItems(listOfItems(position.getListOfItemsAtPosition())).remove(item);
			}
			listOfPositions.clear();
			this.isTerminated = true;
		}
	}

	/**
	 * ...
	 */
	@Basic 
	public boolean isTerminated() {
		return isTerminated;
	}
	
	
	/**
	 * @return the listOfPositions
	 */
	@Basic
	public ArrayList<Position> getListOfPositions() {
		return this.listOfPositions;
	}
	
	/**
	 * Checks whether this board can have the given position as one of its positions.
	 * 
	 * @param position
	 * 			The position to check.
	 * @return ...
	 * 			|( (position != null) && !(this.isTerminated()) && !(position.isTerminated())
	 *			|	&& position.getX() >= getMinOfBoard() && position.getX() <= this.getWidth() 
	 *			|	&& position.getY() >= getMinOfBoard() && position.getY() <= this.getHeight() )
	 */
	@Raw
	public boolean canHaveAsPosition(Position position){
		return ( (position != null) && !(this.isTerminated()) && !(position.isTerminated())
				&& position.getX() >= getMinOfBoard() && position.getX() <= this.getWidth() 
				&& position.getY() >= getMinOfBoard() && position.getY() <= this.getHeight() );
	}
	
	/**
	 * Checks whether this board has the proper positions associated with it.
	 * 
	 * @return ...
	 * 			|result ==
	 * 			|	for each position in this.getListOfPositions:			
	 * 			|		canHaveAsPosition(position)
	 */
	@Raw @Model
	public boolean hasProperPositions(){
		for(Position position: this.getListOfPositions())
			if(!this.canHaveAsPosition(position))
				return false;
		return true;
	}

	/**
	 * Searches for all batteries in list of items on board.
	 * @return ...
	 * 			|for each battery in listOfItemsOnBoard:
	 * 			|	batteries.add((Battery)item)
	 * 			|result == batteries
	 */
	@Basic
	public ArrayList<Battery> getListOfBatteries() {
		ArrayList<Battery> batteries = new ArrayList<Battery>();
        for (Item e : this.getListOfItemsOnBoard()) {
                if (e instanceof Battery) {
                        batteries.add((Battery) e);
                }
        }
        return batteries;
	}
	
	/**
	 * Searches for all walls in list of items on board.
	 * @return ...
	 * 			|for each wall in listOfItemsOnBoard:
	 * 			|	walls.add((Wall)item)
	 * 			|result == walls
	 */
	@Basic
	public ArrayList<Wall> getListOfWalls() {
		 ArrayList<Wall> walls = new ArrayList<Wall>();
         for (Item e : this.getListOfItemsOnBoard()) {
                 if (e instanceof Wall) {
                         walls.add((Wall) e);
                 }
         }
         return walls;
	}

	/**
	 * Searches for all the repairKits in list of items on board.
	 * @return ...
	 * 			|for each repairKits in listOfItemsOnBoard:
	 * 			|	repairkits.add((RepairKit)item)
	 * 			|result == repairKits
	 */
	@Basic
	public ArrayList<RepairKit> getListOfRepairKits() {
		ArrayList<RepairKit> repairKits = new ArrayList<RepairKit>();
        for (Item e : this.getListOfItemsOnBoard()) {
                if (e instanceof RepairKit) {
                        repairKits.add((RepairKit) e);
                }
        }
        return repairKits;
	}

	/**
	 * Searches for all the surpriseBoxes in list of items on board.
	 * @return ...
	 * 			|for each surpriseBoxes in listOfItemsOnBoard:
	 * 			|	surpriseBoxes.add((SurpriseBox)item)
	 * 			|result == surpriseBoxes
	 */
	@Basic
	public ArrayList<SurpriseBox> getListOfSupriseBoxes() {
		 ArrayList<SurpriseBox> surpriseBoxes = new ArrayList<SurpriseBox>();
         for (Item e : this.getListOfItemsOnBoard()) {
                 if (e instanceof SurpriseBox) {
                	 surpriseBoxes.add((SurpriseBox) e);
                 }
         }
         return surpriseBoxes;
	}
	
	/**
	 * Returns the list of item on the board depending of the class of item.
	 * 
	 * @param item
	 * 			The item to check
	 * @return ...
	 * 			|if (item instanceof PickableItem) return listOfPickableItems((PickableItem) item);
	 *			|if (item.getClass() == Wall.class) return getListOfWalls();
	 *			|if (item.getClass() == Robot.class) return getListOfRobots();
	 *			|return null;
	 */
	public ArrayList<? extends Item> listOfItems(Item item){
		if (item instanceof PickableItem) return listOfPickableItems((PickableItem) item);
		if (item.getClass() == Wall.class) return getListOfWalls();
		if (item.getClass() == Robot.class) return getListOfRobots();
		return null;
	}
	
	/**
	 * Returns the list of pickableItems on the board depending of the class of item.
	 * 
	 * @param item
	 * 			The item to check
	 * @return ...
	 * 			|if (item.getClass() == Battery.class) return getListOfBatteries();
	 *			|if (item.getClass() == RepairKit.class) return getListOfRepairKits();
	 *			|if (item.getClass() == SurpriseBox.class) return getListOfSupriseBoxes();
	 *			|return null;
	 */
	public ArrayList<? extends PickableItem> listOfPickableItems(PickableItem item){
		if (item.getClass() == Battery.class) return getListOfBatteries();
		if (item.getClass() == RepairKit.class) return getListOfRepairKits();
		if (item.getClass() == SurpriseBox.class) return getListOfSupriseBoxes();
		return null;
	}

	public Item listOfItems(ArrayList<?> item){
		return (Item) item.get(0);
	}

	public ArrayList<Item> getListOfItemsOnBoard() {
		return listOfItemsOnBoard;
	}
	
	public ConditionIterator getIteratorForCondition(Method condition, Object param) {
        return new ConditionIterator(this, condition, param);
	}
}
